/********************************************************************
	MPX: The MultiProgramming eXecutive
	Project to Accompany
	A Practical Approach to Operating Systems
	Malcolm G. Lane & James D. Mooney
	Copyright 1993, P.W.S. Kent Publishing Co., Boston, MA.

	File Name: trmdrive.c

	Authors: M.G. Lane, A. Ghosal, J. Mooney
	Version: 2.1b
	Date: 11/10/93

	Purpose: Terminal (Console) Driver

	This module is a direct driver for keyboard input and
	screen output.  These devices are collectively called
	the "terminal."  Note that the screen output does *not*
	use interrupts.

	This version does not support simultaneous input and
	output, or typeahead.  Note that the screen driver does
	*not* use interrupts, and does *not* call IO_complete.

	This driver uses BIOS functions for screen output and
	keyboard input.  It should be portable across all
	keyboards and video modes.

	Environments: IBM-PC, TURBO-C.

	Procedures:	trm_open	open the terminal
			trm_close	close the terminal
			trm_read	begin a keyboard read
			trm_getc	process keyboard characters
			trm_write	perform a screen write
                        trm_clear	clear the screen
			trm_gotoxy	set cursor position
			kbd_ihand	handle keyboard interrupts
			trm_getc	get processed characters


**************************************************************************

	Change Log:

	04/21/88  akg	revise IOFIN parameters
	05/08/88  mgl	enhance driver, add tab support.
	05/09/88  mgl	change to far pointers for con_read and con_write
	08/02/88  mgl	enabled code for 8088, 8086 that had been
                              conditionally omitted.
	12/11/92  jdm	restructured for MPX 2.0
	12/30/92  jdm	corrected name conflict (clear_scr)
	03/30/93  jdm	corrected errors in trm_getc
	03/30/93  jdm	final version for V2.0b
        11/10/93  jdm	updated for large model; removed IO_complete

**************************************************************************/

#include <dos.h>

typedef unsigned short word;
typedef unsigned char byte;

typedef struct context {
	word BP, DI, SI, DS, ES;
	word DX, CX, BX, AX;
	word IP, CS, FLAGS;
	} context;

#include "mpx_supt.h"
#include "trmdrive.h"

/* define 8259 PIC ports */
#define	PIC_CMD		0x20
#define PIC_MASK	0x21

/* keyboard interrupt level */
#define KBD_LEVEL 1


/* general definitions */
#define SET		1
#define RESET		0
#define	CR		0x0D
#define	LF		0x0A
#define BS		0x08
#define	ESC		0x1B

/* DCB status codes */
#define DEV_IDLE	0
#define	DEV_READ	1
#define DEV_WRITE	2

/* define keyboard interrupt number */
#define KBD_INTNUM	(0x08 + KBD_LEVEL)

/* MSDOS interrupt parameters */
#define OPEN_FILE	0x3D
#define CLOSE_FILE	0x3E
#define	WRITE_FILE	0x40
#define	GET_CHAR	0x06

#define WRITE_ONLY	0x01

#define MAX_XPOS	79
#define	MAX_YPOS	23

/* register structures for MS-DOS interrupts */
union REGS regs;
struct SREGS segs;

/* DCB structure */
struct {
	flag	open;
	int	status;
	int	*eflag_p;
	char	*out_buf_p;
	int	*out_count_p;
	int	out_max;
	int	out_ctr;
	char	*in_buf_p;
	int	*in_count_p;
	int	in_max;
	int	in_ctr;
	} dcb_trm = {FALSE, DEV_IDLE};

/* ptr to saved keyboard interrupt vector */
void interrupt (* old_kbhand_p) (void) = NULL;

/* MS-DOS handle for console (screen) output */
int con_handle;

/* pending keyboard character count */
int pendc = 0;

/*extern flag mpx_active;*/

/* declare keyboard interrupt handler */
void interrupt kbd_ihand(void);

/* declare local procedures */
void clear_scr (void);
int goto_xy (int xval, int yval);
void out_char(char ch);


/*
	Procedure: trm_open

	Purpose: initialize the terminal


	Parameters: 	int *ef_p	ptr to event flag

	Return value: error code, or zero if OK

	Calls:	getvect, setvect
		clear_scr
		intdos(OPEN_FILE)

	Globals: old_kbhand_p, dcb_trm, regs

	Errors:	ERR_TRM_OP_INVEFP invalid event flag parameter
		ERR_TRM_OP_ALROPN device already open
		ERR_TRM_OP_OPFAIL open failed

	The keyboard is assumed to be already "open", with interrupts
	enabled.  We insert the MPX interrupt handler so keystrokes
	will be detected;  this will in turn call the MS-DOS handler
	for scan code processing, then collect the processed code.

	The screen is accessed through MS-DOS handles.  We open a
	private handle for CON and clear the screen.  We assume that
	ANSI.SYS is loaded.
*/
int trm_open (int *ef_p)
{
	int	rval;
	int	err;

	/* validate parameter */
	if (ef_p == NULL) return (ERR_TRM_OP_INVEFP);

	/* if already open, return error code */
	if (dcb_trm.open) return (ERR_TRM_OP_ALROPN);

	/* initialize DCB */
	dcb_trm.eflag_p = ef_p;
	dcb_trm.open = TRUE;
	dcb_trm.status = DEV_IDLE;

	/* clear character counter */
	pendc = 0;

	/* open CON for output, get handle */
	regs.h.ah = (byte) OPEN_FILE;
	regs.h.al = (byte) WRITE_ONLY;
	regs.x.dx = (word) "CON";
	rval = intdos(&regs, &regs);
	if (regs.x.cflag!=0) return(ERR_TRM_OP_OPFAIL);
	con_handle = rval;

	/* save MS-DOS keyboard handler addr, setup MPX handler */
	if (old_kbhand_p==NULL) {
		old_kbhand_p = getvect(KBD_INTNUM);
	}
	setvect(KBD_INTNUM,&kbd_ihand);

	/* clear the screen */
/*	err = trm_clear();
	err = err;*/

	return (OK);
}


/*
	Procedure: trm_close

	Purpose: close the terminal


	Parameters: 	none

	Return value: error code, or zero if OK

	Calls:	setvect
		intdos (CLOSE_FILE)

	Globals: old_kbhand_p, dcb_trm, regs

	Errors:	ERR_TRM_CL_NOTOPN	terminal not open
		ERR_TRM_CL_CLFAIL	close failed
*/
trm_close(void)
{
	int	err;

	/* if not open, return error code */
	if (dcb_trm.eflag_p == NULL) return(ERR_TRM_CL_NOTOPN);

	/* clear the open flag */
	dcb_trm.open = FALSE;

	/* restore MS-DOS interrupt vector */
	if (old_kbhand_p!=NULL) {
		setvect(KBD_INTNUM, old_kbhand_p);
	}
	old_kbhand_p = NULL;

	/* close CON */
	regs.h.ah = (byte) CLOSE_FILE;
	regs.x.bx = (word) con_handle;
	err = intdos(&regs, &regs);
	err = err;
	if (regs.x.cflag!=0) return(ERR_TRM_CL_CLFAIL);

	return(OK);
}



/*
	Procedure: trm_read

	Purpose: initiate block input from the keyboard

	Parameters: 	char *buf_p	ptr to buffer
			int *count_p ptr to count

	Return value: error code, or zero if OK

	Calls:	out_char

	Globals: dcb_trm

	Errors:	ERR_TRM_RD_INVBUF invalid buffer parameter
		ERR_TRM_RD_INVCNT invalid count parameter
		ERR_TRM_RD_NOTOPN device not open
		ERR_TRM_RD_DVBUSY device busy
*/
int trm_read (char *buf_p, int *count_p)
{
	/* check for valid parameters */
	if (buf_p == NULL) return (ERR_TRM_RD_INVBUF);
	if (count_p == NULL) return (ERR_TRM_RD_INVCNT);
	if (*count_p <= 0) return (ERR_TRM_RD_INVCNT);

	/* check terminal status */
	if (dcb_trm.eflag_p == NULL) return(ERR_TRM_RD_NOTOPN);
	if (dcb_trm.status != DEV_IDLE) return(ERR_TRM_RD_DVBUSY);

	/* setup DCB */
	dcb_trm.in_buf_p = buf_p;
	*dcb_trm.in_buf_p = NULCH;
	dcb_trm.in_count_p = count_p;
	dcb_trm.in_max = *count_p;
	dcb_trm.in_ctr = 0;
	dcb_trm.status = DEV_READ;

	/* clear caller's event flag */
	*dcb_trm.eflag_p = RESET;

	return (OK);
}


/*
	Procedure: trm_write

	Purpose: perform block output to the screen

	This routine is NOT interrupt driven, and does
	NOT call IO_complete!


	Parameters: 	char *buf_p	ptr to buffer
			int *count_p ptr to count

	Return value: error code, or zero if OK

	Calls:	out_char

	Globals: dcb_trm

	Errors:	ERR_TRM_WR_INVBUF invalid buffer parameter
		ERR_TRM_WR_INVCNT invalid count parameter
		ERR_TRM_WR_NOTOPN device not open
		ERR_TRM_WR_DVBUSY device busy
*/
int trm_write (char *buf_p, int *count_p)
{

	char	ch;

	/* check for valid parameters */
	if (buf_p == NULL) return (ERR_TRM_WR_INVBUF);
	if (count_p == NULL) return (ERR_TRM_WR_INVCNT);
	if (*count_p < 0) return (ERR_TRM_WR_INVCNT);

	/* check terminal status */
	if (dcb_trm.eflag_p == NULL) return(ERR_TRM_WR_NOTOPN);
	if (dcb_trm.status != DEV_IDLE) return(ERR_TRM_WR_DVBUSY);

	/* setup DCB */
	dcb_trm.out_buf_p = buf_p;
	dcb_trm.out_count_p = count_p;
	dcb_trm.out_max = *count_p;
	dcb_trm.out_ctr = 0;
	dcb_trm.status = DEV_WRITE;

	/* clear caller's event flag */
	*dcb_trm.eflag_p = RESET;

	/* output the characters */
	while (dcb_trm.out_ctr < dcb_trm.out_max) {
		ch = *dcb_trm.out_buf_p++;
		dcb_trm.out_ctr++;
		out_char(ch);
		if (ch==CR) out_char(LF);
		if (ch==LF) out_char(CR);
	}

	/* reset DCB status */
	dcb_trm.status = DEV_IDLE;

	/* return count, set event flag */
	*dcb_trm.out_count_p = dcb_trm.out_ctr;
	*dcb_trm.eflag_p = SET;

	return (OK);
}



/*
	Procedure: trm_clear

	Purpose: clear the terminal screen


	Parameters: 	none

	Return value: error code, or zero if OK

	Calls:	clear_scr

	Globals: none

	Errors:	none
*/

int trm_clear(void)
{
	int err;

	err = goto_xy(0,0);
	err = err;
	clear_scr();

	return(OK);
}

/*
	Procedure: trm_gotoxy

	Purpose: position cursor


	Parameters: 	int xval	requested x position (0 - 79}
			int yval	requested y position (0 - 23)

	Return value: error code, or zero if OK

	Calls:	gotoxy

	Globals: none

	Errors:	ERR_TRM_XY_INVPOS invalid x or y parameter
*/

int trm_gotoxy(int xval, int yval)
{
	int err;

	err = goto_xy(xval, yval);
	if (err != OK) return(ERR_TRM_XY_INVPOS);

	return(OK);
}


/*
	Procedure: kbd_ihand

	Purpose: keyboard interrupt handler


	Parameters: none

	Return value: none

	Calls:	MSDOS keyboard handler

	Globals: old_kbhand_p, pendc
*/
void interrupt kbd_ihand(void)
{
	/*if (mpx_active) return;*/

	/* let MS-DOS process the character */
	(*old_kbhand_p)();

	/* increment counter */
	pendc++;


}


/*
	Procedure: trm_getc

	Purpose: process pending keyboard characters


	Parameters: 	none

	Return value: none

	Calls:	intdos(GET_CHAR)
		out_char
		IO_complete

	Globals: dcb_trm, regs
		pendc

	Errors:	none
*/
void trm_getc(void)
{
	char	nextch;
	int	err;
	int 	finish;


	/* process any pending characters until block finished */
	finish = FALSE;
	while ((pendc > 0) && !finish) {

     		/* get the processed character, if any */
		regs.h.ah = (byte) GET_CHAR;
		regs.h.dl = 0xFF;
		err = intdos(&regs, &regs);
		err = err;
		nextch = (char) regs.h.al;


		/* if no character present, ignore */
		if (regs.x.flags & 0x40){
		       /*pendc = 1;*/
		}


		/* if char = 0, get the function code & ignore */
		else if (nextch==NULCH) {
			regs.h.ah = (byte) GET_CHAR;
			regs.h.dl = 0xFF;
			err = intdos(&regs, &regs);
			err = err;
		}


		else {
			/* if CR, store newline & advance cursor */
			if (nextch==CR) {
				out_char(CR);
				out_char(LF);
				*dcb_trm.in_buf_p++ = '\n';
				dcb_trm.in_ctr++;
			}

			/* if backspace, delete prev. char, if any */
			else if (nextch==BS) {
				if (dcb_trm.in_ctr > 0) {
					out_char(BS);
					out_char(' ');
					out_char(BS);
					dcb_trm.in_ctr--;
					dcb_trm.in_buf_p--;
				}
			}

			/* otherwise, just store & echo */
			else {
				out_char(nextch);
				*dcb_trm.in_buf_p++ = nextch;
				dcb_trm.in_ctr++;
			}

			*dcb_trm.in_buf_p = NULCH;

			/* terminate on CR (ENTER) */
			if (nextch == CR)  {
				finish = TRUE;
			}

			/* otherwise, terminate if buffer full */
			else if (dcb_trm.in_ctr >= dcb_trm.in_max) {
				out_char(CR);
				out_char(LF);
				finish = TRUE;
			}
		}

	/* decrement character counter */
	pendc--;
	}

	/* cleanup if terminating */
	if (finish) {
		dcb_trm.status = DEV_IDLE;
		*dcb_trm.in_count_p = dcb_trm.in_ctr;
		*dcb_trm.eflag_p = SET;
	}

	return;
}


/*
	Procedure: clear_scr

	Purpose: clear the terminal screen


	Parameters: none

	Return value: none

	Calls:	out_char

	Globals: none
*/
void clear_scr(void)
{

	out_char(ESC);
	out_char('[');
	out_char('2');
	out_char('J');

}

/*
	Procedure: goto_xy

	Purpose: position the cursor

	Parameters:	int xval	horizontal position (0 - 79)
			int yval	vertical position (0 - 23)

	Return value: error code; zero if OK

	Calls: out_char

	Globals: none
*/
int goto_xy(int xval, int yval)
{

	int xdh, xdl;
	int ydh, ydl;
	char digtab[10] = {'0','1','2','3','4','5','6','7','8','9'};

	if ((xval < 0) || (xval > MAX_XPOS)) return(-1);
	if ((yval < 0) || (yval > MAX_YPOS)) return(-1);

	xdh = (xval%100)/10;
	xdl = (xval%10);
	ydh = (yval%100)/10;
	ydl = (yval%10);

	out_char(ESC);
	out_char('[');
	out_char(digtab[ydh]);
	out_char(digtab[ydl]);
	out_char(';');
	out_char(digtab[xdh]);
	out_char(digtab[xdl]);
	out_char('H');



	return(OK);
}

/*
	Procedure: out_char

	Purpose: output a character


	Parameters: ch	character to output

	Return value: none

	Calls:	intdosx (WRITE_FILE)

	Globals: none
*/
void out_char(ch)
char ch;
{
	char	chbuf;
	int	err;

	chbuf = ch;
	regs.h.ah = (byte) WRITE_FILE;
	regs.x.bx = con_handle;
	regs.x.cx = 1;
	regs.x.dx = FP_OFF(&chbuf);
        segs.ds = FP_SEG(&chbuf);
        segs.es = FP_SEG(&chbuf);
	err = intdosx(&regs, &regs, &segs);
	err = err;
}

/* END OF FILE */



